"""
Tool registration and management system for CodeMCP.

Provides centralized registration, discovery, and management of MCP tools
across all language-specific and document analysis components.
"""

import inspect
from typing import Dict, Any, List, Optional, Callable, Type, Union
from dataclasses import dataclass, field
from abc import ABC, abstractmethod
import asyncio
from enum import Enum
from .error_handler import CodeMCPError, log_info, log_warning, log_debug


class ToolType(Enum):
    """Types of tools available in CodeMCP."""
    ANALYSIS = "analysis"
    PARSING = "parsing"
    NAVIGATION = "navigation"
    EXPORT = "export"
    UTILITY = "utility"


class ToolStatus(Enum):
    """Status of registered tools."""
    REGISTERED = "registered"
    ACTIVE = "active"
    DISABLED = "disabled"
    ERROR = "error"


@dataclass
class ToolParameter:
    """Tool parameter definition."""
    name: str
    type: Type
    description: str
    required: bool = True
    default: Any = None
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert parameter to dictionary."""
        return {
            "name": self.name,
            "type": self.type.__name__,
            "description": self.description,
            "required": self.required,
            "default": self.default
        }


@dataclass
class ToolDefinition:
    """Definition of a registered tool."""
    name: str
    description: str
    function: Callable
    tool_type: ToolType
    parameters: List[ToolParameter] = field(default_factory=list)
    language: Optional[str] = None
    version: str = "1.0.0"
    author: Optional[str] = None
    tags: List[str] = field(default_factory=list)
    status: ToolStatus = ToolStatus.REGISTERED
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert tool definition to dictionary."""
        return {
            "name": self.name,
            "description": self.description,
            "type": self.tool_type.value,
            "parameters": [p.to_dict() for p in self.parameters],
            "language": self.language,
            "version": self.version,
            "author": self.author,
            "tags": self.tags,
            "status": self.status.value
        }


class ToolRegistry:
    """Central registry for all CodeMCP tools."""
    
    def __init__(self):
        self.tools: Dict[str, ToolDefinition] = {}
        self.tool_groups: Dict[str, List[str]] = {}
        self.dependencies: Dict[str, List[str]] = {}
        
    def register_tool(self,
                     name: str,
                     function: Callable,
                     description: str,
                     tool_type: ToolType,
                     language: Optional[str] = None,
                     version: str = "1.0.0",
                     author: Optional[str] = None,
                     tags: List[str] = None,
                     dependencies: List[str] = None) -> ToolDefinition:
        """
        Register a new tool in the registry.
        
        Args:
            name: Unique tool name
            function: The callable function
            description: Tool description
            tool_type: Type of tool
            language: Target language (for language-specific tools)
            version: Tool version
            author: Tool author
            tags: Tool tags for categorization
            dependencies: List of required tools
            
        Returns:
            ToolDefinition instance
        """
        if name in self.tools:
            raise CodeMCPError(f"Tool '{name}' is already registered")
        
        # Extract parameters from function signature
        parameters = self._extract_parameters(function)
        
        # Create tool definition
        tool_def = ToolDefinition(
            name=name,
            description=description,
            function=function,
            tool_type=tool_type,
            parameters=parameters,
            language=language,
            version=version,
            author=author,
            tags=tags or [],
            status=ToolStatus.REGISTERED
        )
        
        # Register the tool
        self.tools[name] = tool_def
        
        # Register dependencies
        if dependencies:
            self.dependencies[name] = dependencies
        
        # Add to appropriate groups
        self._add_to_groups(name, tool_def)
        
        log_info(f"Registered tool: {name} ({tool_type.value})")
        return tool_def
    
    def _extract_parameters(self, function: Callable) -> List[ToolParameter]:
        """Extract parameters from function signature."""
        parameters = []
        sig = inspect.signature(function)
        
        for param_name, param in sig.parameters.items():
            # Skip 'self' parameter
            if param_name == 'self':
                continue
                
            # Determine parameter type
            param_type = param.annotation if param.annotation != inspect.Parameter.empty else Any
            
            # Check if parameter is required
            required = param.default == inspect.Parameter.empty
            
            # Get default value
            default = param.default if not required else None
            
            # Create parameter definition
            tool_param = ToolParameter(
                name=param_name,
                type=param_type,
                description=f"Parameter {param_name}",  # TODO: Extract from docstring
                required=required,
                default=default
            )
            
            parameters.append(tool_param)
        
        return parameters
    
    def _add_to_groups(self, name: str, tool_def: ToolDefinition):
        """Add tool to appropriate groups."""
        # Group by type
        type_group = f"type:{tool_def.tool_type.value}"
        if type_group not in self.tool_groups:
            self.tool_groups[type_group] = []
        self.tool_groups[type_group].append(name)
        
        # Group by language
        if tool_def.language:
            lang_group = f"language:{tool_def.language}"
            if lang_group not in self.tool_groups:
                self.tool_groups[lang_group] = []
            self.tool_groups[lang_group].append(name)
        
        # Group by tags
        for tag in tool_def.tags:
            tag_group = f"tag:{tag}"
            if tag_group not in self.tool_groups:
                self.tool_groups[tag_group] = []
            self.tool_groups[tag_group].append(name)
    
    def unregister_tool(self, name: str):
        """Unregister a tool from the registry."""
        if name not in self.tools:
            raise CodeMCPError(f"Tool '{name}' is not registered")
        
        # Remove from groups
        tool_def = self.tools[name]
        self._remove_from_groups(name, tool_def)
        
        # Remove dependencies
        if name in self.dependencies:
            del self.dependencies[name]
        
        # Remove the tool
        del self.tools[name]
        
        log_info(f"Unregistered tool: {name}")
    
    def _remove_from_groups(self, name: str, tool_def: ToolDefinition):
        """Remove tool from all groups."""
        for group_name, tool_list in self.tool_groups.items():
            if name in tool_list:
                tool_list.remove(name)
        
        # Clean up empty groups
        empty_groups = [group for group, tools in self.tool_groups.items() if not tools]
        for group in empty_groups:
            del self.tool_groups[group]
    
    def get_tool(self, name: str) -> Optional[ToolDefinition]:
        """Get tool definition by name."""
        return self.tools.get(name)
    
    def list_tools(self, 
                  tool_type: Optional[ToolType] = None,
                  language: Optional[str] = None,
                  status: Optional[ToolStatus] = None,
                  tags: Optional[List[str]] = None) -> List[ToolDefinition]:
        """
        List tools matching criteria.
        
        Args:
            tool_type: Filter by tool type
            language: Filter by language
            status: Filter by status
            tags: Filter by tags (all tags must match)
            
        Returns:
            List of matching tool definitions
        """
        tools = list(self.tools.values())
        
        # Filter by type
        if tool_type:
            tools = [t for t in tools if t.tool_type == tool_type]
        
        # Filter by language
        if language:
            tools = [t for t in tools if t.language == language]
        
        # Filter by status
        if status:
            tools = [t for t in tools if t.status == status]
        
        # Filter by tags
        if tags:
            tools = [t for t in tools if all(tag in t.tags for tag in tags)]
        
        return tools
    
    def get_tools_by_group(self, group: str) -> List[ToolDefinition]:
        """Get tools by group name."""
        if group not in self.tool_groups:
            return []
        
        tool_names = self.tool_groups[group]
        return [self.tools[name] for name in tool_names if name in self.tools]
    
    def list_groups(self) -> List[str]:
        """List all available groups."""
        return list(self.tool_groups.keys())
    
    def validate_dependencies(self, tool_name: str) -> bool:
        """Check if all tool dependencies are satisfied."""
        if tool_name not in self.dependencies:
            return True
        
        for dep in self.dependencies[tool_name]:
            if dep not in self.tools:
                log_warning(f"Tool '{tool_name}' dependency '{dep}' not found")
                return False
            
            if self.tools[dep].status == ToolStatus.ERROR:
                log_warning(f"Tool '{tool_name}' dependency '{dep}' is in error state")
                return False
        
        return True
    
    def enable_tool(self, name: str):
        """Enable a tool."""
        if name not in self.tools:
            raise CodeMCPError(f"Tool '{name}' is not registered")
        
        if not self.validate_dependencies(name):
            raise CodeMCPError(f"Tool '{name}' dependencies not satisfied")
        
        self.tools[name].status = ToolStatus.ACTIVE
        log_info(f"Enabled tool: {name}")
    
    def disable_tool(self, name: str):
        """Disable a tool."""
        if name not in self.tools:
            raise CodeMCPError(f"Tool '{name}' is not registered")
        
        self.tools[name].status = ToolStatus.DISABLED
        log_info(f"Disabled tool: {name}")
    
    def set_tool_error(self, name: str, error: str):
        """Mark a tool as having an error."""
        if name not in self.tools:
            raise CodeMCPError(f"Tool '{name}' is not registered")
        
        self.tools[name].status = ToolStatus.ERROR
        log_warning(f"Tool '{name}' marked as error: {error}")
    
    async def call_tool(self, name: str, **kwargs) -> Any:
        """
        Call a registered tool with parameters.
        
        Args:
            name: Tool name
            **kwargs: Tool parameters
            
        Returns:
            Tool execution result
        """
        if name not in self.tools:
            raise CodeMCPError(f"Tool '{name}' is not registered")
        
        tool_def = self.tools[name]
        
        # Check tool status
        if tool_def.status != ToolStatus.ACTIVE:
            if tool_def.status == ToolStatus.DISABLED:
                raise CodeMCPError(f"Tool '{name}' is disabled")
            elif tool_def.status == ToolStatus.ERROR:
                raise CodeMCPError(f"Tool '{name}' is in error state")
            else:
                # Auto-enable if registered
                self.enable_tool(name)
        
        # Validate parameters
        self._validate_parameters(tool_def, kwargs)
        
        try:
            # Call the tool function
            if asyncio.iscoroutinefunction(tool_def.function):
                result = await tool_def.function(**kwargs)
            else:
                result = tool_def.function(**kwargs)
            
            log_debug(f"Successfully called tool: {name}")
            return result
            
        except Exception as e:
            self.set_tool_error(name, str(e))
            raise CodeMCPError(f"Tool '{name}' execution failed: {e}")
    
    def _validate_parameters(self, tool_def: ToolDefinition, kwargs: Dict[str, Any]):
        """Validate tool parameters."""
        # Check required parameters
        for param in tool_def.parameters:
            if param.required and param.name not in kwargs:
                raise CodeMCPError(f"Required parameter '{param.name}' missing for tool '{tool_def.name}'")
        
        # Check parameter types (basic validation)
        for param_name, value in kwargs.items():
            param_def = next((p for p in tool_def.parameters if p.name == param_name), None)
            if param_def and param_def.type != Any:
                if not isinstance(value, param_def.type):
                    log_warning(f"Parameter '{param_name}' type mismatch for tool '{tool_def.name}'")
    
    def get_registry_info(self) -> Dict[str, Any]:
        """Get comprehensive registry information."""
        return {
            "total_tools": len(self.tools),
            "tools_by_type": {
                tool_type.value: len(self.list_tools(tool_type=tool_type))
                for tool_type in ToolType
            },
            "tools_by_status": {
                status.value: len(self.list_tools(status=status))
                for status in ToolStatus
            },
            "languages": list(set(tool.language for tool in self.tools.values() if tool.language)),
            "groups": self.list_groups(),
            "dependency_count": len(self.dependencies)
        }
    
    def export_registry(self) -> Dict[str, Any]:
        """Export registry as dictionary."""
        return {
            "tools": {name: tool.to_dict() for name, tool in self.tools.items()},
            "groups": self.tool_groups,
            "dependencies": self.dependencies,
            "info": self.get_registry_info()
        }


# Global tool registry instance
_global_registry = None


def get_tool_registry() -> ToolRegistry:
    """Get the global tool registry instance."""
    global _global_registry
    if _global_registry is None:
        _global_registry = ToolRegistry()
    return _global_registry


def tool(name: str,
         description: str,
         tool_type: ToolType,
         language: Optional[str] = None,
         version: str = "1.0.0",
         author: Optional[str] = None,
         tags: List[str] = None,
         dependencies: List[str] = None):
    """
    Decorator for registering tools.
    
    Args:
        name: Tool name
        description: Tool description
        tool_type: Type of tool
        language: Target language
        version: Tool version
        author: Tool author
        tags: Tool tags
        dependencies: Tool dependencies
    """
    def decorator(func: Callable):
        registry = get_tool_registry()
        registry.register_tool(
            name=name,
            function=func,
            description=description,
            tool_type=tool_type,
            language=language,
            version=version,
            author=author,
            tags=tags,
            dependencies=dependencies
        )
        return func
    return decorator